﻿/*
 * Copyright 2012 SymSoftSolutions

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 * */
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Linq;
using System.Net;
using System.Web.Script.Serialization;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace GoogleOAuth2
{

    public class ServiceAccount
    {
        public const string GOOGLE_OAUTH2_ENDPOINT = "https://accounts.google.com/o/oauth2/token";
        private const string HEADER_CONTENT_TYPE = @"application/x-www-form-urlencoded";
        private const string GRANT_TYPE = @"assertion";
        private const string ASSERTION_TYPE = @"http://oauth.net/grant_type/jwt/1.0/bearer";
        private const string DEFAULT_SECRET = "notasecret";

        public static string getAccesstoken(string email, string certPath, string scope, string secrete = DEFAULT_SECRET)
        {
            string retval = "";
            WebClient client = new WebClient();
            string assertion = ServiceAccountUtils.getAssertion(email, certPath, scope, secrete);
            NameValueCollection formData = new NameValueCollection(){
                {"grant_type", GRANT_TYPE},
                {"assertion_type", ASSERTION_TYPE},
                {"assertion", assertion}
            };
            client.Headers["Content-type"] = HEADER_CONTENT_TYPE;

            
            try
            {
                byte[] responseBytes = client.UploadValues(GOOGLE_OAUTH2_ENDPOINT, "POST", formData);
                string responseStr = Encoding.UTF8.GetString(responseBytes);


                JavaScriptSerializer js = new JavaScriptSerializer();
                Dictionary<string, object> token;
                token = js.Deserialize<Dictionary<string, object>>(responseStr);
                retval = (string)token["access_token"];
            }
            catch (WebException ex)
            {
                HttpWebResponse r = (HttpWebResponse)ex.Response;
                throw new Exception("Google responded with an error. Message from Google: " + r.StatusDescription, ex);
            }
            return retval;
        }
    }

    public static class ServiceAccountUtils
    {
        private const int TOKEN_VALID_FOR = 60 * 60;


        public static string getAssertion(string email, string certPath, string scope, string secret)
        {
            string claimset = base64Encoding(getClaimset(email, scope));
            string header = base64Encoding(getHeader("RS256", "JWT"));
            string payload = header + "." + claimset;
            byte[] signature = rs256Signature(payload, certPath, secret);

            string assertion = header + "." + claimset + "." + base64Encoding(signature);

            return assertion;
        }

        public static string getHeader(string algorithm, string type)
        {
            Dictionary<string, object> header = new Dictionary<string, object>();
            header.Add("alg", algorithm);
            header.Add("typ", type);

            string jwtHeader = jsonSerialize(header);
            return jwtHeader;
        }

        public static string getClaimset(string email, string scope)
        {
            string iss = email;
            string aud = ServiceAccount.GOOGLE_OAUTH2_ENDPOINT;

            DateTime utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
            DateTime issueTime = DateTime.UtcNow;

            int iat = (int)issueTime.Subtract(utc0).TotalSeconds;
            int exp = iat + TOKEN_VALID_FOR;

            Dictionary<string, object> jwtDict = new Dictionary<string, object>();
            jwtDict.Add("iss", iss);
            jwtDict.Add("scope", scope);
            jwtDict.Add("aud", aud);
            jwtDict.Add("exp", exp);
            jwtDict.Add("iat", iat);

            return jsonSerialize(jwtDict);
        }

        public static byte[] rs256Signature(string message, string certPath, string secret)
        {
            byte[] signature;
            byte[] toSign = getByte(message);

            X509Certificate2 cert = new X509Certificate2(certPath, secret);
            CspParameters cp = new CspParameters(24, "Microsoft Enhanced RSA and AES Cryptographic Provider",
                ((RSACryptoServiceProvider)cert.PrivateKey).CspKeyContainerInfo.KeyContainerName);
            RSACryptoServiceProvider provider = new RSACryptoServiceProvider(cp);

            signature = provider.SignData(toSign, "SHA256");

            return signature;  
        }

        public static string base64Encoding(string input)
        {
            return base64Encoding(getByte(input));
        }

        public static string base64Encoding(byte[] input)
        {
            string output = Convert.ToBase64String(input);

            output = output.Split('=')[0]; // Remove any trailing '='s
            output = output.Replace('+', '-');
            output = output.Replace('/', '_');
            return output;
        }

        public static string jsonSerialize(Dictionary<string, object> keyval)
        {
            JavaScriptSerializer jsonSerialize = new JavaScriptSerializer();
            return jsonSerialize.Serialize(keyval);
        }

        private static byte[] getByte(string str)
        {
            return Encoding.UTF8.GetBytes(str);
        }
    }
}
